﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using EasyHook;
using System.Windows.Forms;
using System.Drawing.Win32;
using System.Diagnostics;
namespace Inject
{

    public class Injector : EasyHook.IEntryPoint
    {
        public InjectClient Interface = null;
        public LocalHook CreateFileHook = null;
        public LocalHook ExtTextOutHook = null;
        public static StreamWriter Log = null;

        Stack<CallEventArgs> Queue = new Stack<CallEventArgs>();

        public Injector(
            RemoteHooking.IContext InContext,
            String InChannelName)
        {
            if(null==Log)
                Log = new StreamWriter("Injector.log");
            Log.AutoFlush = true;
            Log.WriteLine("Log Started {0}",DateTime.Now);
            Interface = RemoteHooking.IpcConnectClient<InjectClient>(InChannelName);

            Interface.Ping(RemoteHooking.GetCurrentProcessId());
        }

        public void Run(
            RemoteHooking.IContext InContext,
            String InArg1)
        {
            try
            {
                Log.WriteLine("Injector.Run {0}", InArg1);

                ExtTextOutHook = LocalHook.Create(
                    LocalHook.GetProcAddress("gdi32.dll", "ExtTextOutW"),
                    new DExtTextOutW(ExtTextOutW_Hooked),
                    this);

                /*
                 * Don't forget that all hooks will start deaktivated...
                 * The following ensures that all threads are intercepted:
                 */
                ExtTextOutHook.ThreadACL.SetExclusiveACL(new Int32[1]);

                Log.WriteLine("ExtTextOutW hooked");
            }
            catch (Exception e)
            {
                /*
                    Now we should notice our host process about this error...
                 */
                Interface.ReportError(RemoteHooking.GetCurrentProcessId(), e);
                Log.WriteLine("Exception in Injector.Run {0}", e);
                return;
            }


            // wait for host process termination...
            try
            {
                Log.Write("Getting process...");
                Process myProcess = Process.GetCurrentProcess();
                IntPtr hMainWnd = myProcess.MainWindowHandle;
                Log.WriteLine("PID={0}, hMainWnd={1}", myProcess.Id, hMainWnd);

                // send enum windows
                EnumWindows enuWnd = new EnumWindows();
                enuWnd.GetWindows(hMainWnd);

                WinWnd mainWnd = new WinWnd(hMainWnd);
                Log.WriteLine("MAIN WINDOW ({1} childs): {0}", mainWnd,enuWnd.Items.Count);

                Interface.FireWindowEvent(new WindowEventArgs(WindowEventType.Add, myProcess.Id, mainWnd, null));

                foreach (WinWnd wnd in enuWnd.Items)
                {
                    Log.WriteLine("WINDOW: {0}",wnd);
                    Interface.FireWindowEvent(new WindowEventArgs(WindowEventType.Add, myProcess.Id, wnd, enuWnd.Items));
                }

                while (Interface.Ping(RemoteHooking.GetCurrentProcessId()))
                {
                    Log.WriteLine("Waiting queue...");
                    Log.Flush();
                    Thread.Sleep(500);

                    // transmit newly monitored file accesses...
                    lock (Queue)
                    {
                        while (Queue.Count > 0)
                        {
                            CallEventArgs args = Queue.Pop();
                            Log.WriteLine("InjectedCall: {0}", args);

                            args.Process = RemoteHooking.GetCurrentProcessId();

                            Interface.FireCallEvent(args);
                        }
                    }
                }
                Log.WriteLine("Exiting Injector...");
            }
            catch (Exception e)
            {
                Log.WriteLine("Exiting Injector on exception: {0}", e);
                // NET Remoting will raise an exception if host is unreachable
            }
        }
        #region
  /*      [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        static extern IntPtr CreateFile(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);*/
        #endregion
        #region CreateFile
        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet=CharSet.Unicode, SetLastError=true)]
        delegate IntPtr DCreateFile(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);

        delegate void DCreateFileAsync(
            Int32 InClientPID,
            IntPtr InHandle,
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile);

     

        // this is where we are intercepting all file accesses!
        static IntPtr CreateFile_Hooked(
            String InFileName,
            UInt32 InDesiredAccess,
            UInt32 InShareMode,
            IntPtr InSecurityAttributes,
            UInt32 InCreationDisposition,
            UInt32 InFlagsAndAttributes,
            IntPtr InTemplateFile)
        {
            try
            {
                Injector This = (Injector)HookRuntimeInfo.Callback;

                lock (This.Queue)
                {
                    if(This.Queue.Count < 1000)
                        This.Queue.Push(new CallEventArgs("CreateFile",null, InFileName, InDesiredAccess, InShareMode, InSecurityAttributes, InCreationDisposition, InFlagsAndAttributes, InTemplateFile));
                }
            }
            catch
            {
            }

            // call original API...
            return Api.CreateFile(
                InFileName,
                InDesiredAccess,
                InShareMode,
                InSecurityAttributes,
                InCreationDisposition,
                InFlagsAndAttributes,
                InTemplateFile);
        }
        #endregion

        #region ExtTextOutW

        [UnmanagedFunctionPointer(CallingConvention.StdCall,
         CharSet = CharSet.Auto,
          SetLastError = true)]
        delegate bool DExtTextOutW(
            IntPtr hdc,
            int X,
            int y,
            uint fuOptions,
            [In] ref IntPtr lprc,
            string lpString,
            uint cbCount,
            [In] IntPtr lpDx
            );

        /* just use a P-Invoke implementation to get native API access from C# (this step is not necessary for C++.NET)
        [DllImport("gdi32.dll",
  ]
        static extern bool ExtTextOutW(IntPtr hdc,
                                       int X,
                                       int Y,
                                       uint fuOptions,
                                       [In] ref IntPtr lprc,
                                       string lpString,
                                       uint cbCount,
                                       [In] IntPtr lpDx);*/

        // this is where we are intercepting all file accesses!
        static bool ExtTextOutW_Hooked(
            IntPtr hdc,
            int X,
            int Y,
            uint fuOptions,
            [In] ref IntPtr lprc,
            string lpString,
            uint cbCount,
            [In] IntPtr lpDx)
        {
            try
            {
                Injector This = (Injector)HookRuntimeInfo.Callback;
                
                Log.Write("* ExtTextOutW");

                IntPtr hwnd = Api.WindowFromDC(hdc);
                
                Log.WriteLine(" WindowFromDC({0})={1}", hdc, hwnd); 
                
                lock (This.Queue)
                {
                    if (This.Queue.Count < 1000)
                        This.Queue.Push(new CallEventArgs("ExtTextOutW", null, hwnd, hdc, X, Y, fuOptions, lprc, lpString, cbCount, lpDx));
                }
            }
            catch
            {
            }

            // call original API...
            return Api.ExtTextOutW(
                 hdc,
                 X,
                 Y,
                 fuOptions,
                 ref lprc,
                 lpString,
                 cbCount,
                 lpDx
                  );

        }
        #endregion
    }
}
